#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
//#include <netinet/in.h>
//#include <netpacket/packet.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/if_packet.h>
#include <netinet/if_ether.h>
#include "options.h"
#include "socket.h"
#include "ieee.h"

int fd;
void engine_recv_handle(unsigned char *args, const unsigned char *data)
{
    //static unsigned long long cnt = 0;
    //printf("%d\r", ++cnt);
    handle_ieee_802_3x(data);
    fflush(stdout);
}

int engine_recv_init(void)
{
    struct ifreq req;
    if((fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
        return -1;
    }
    strncpy(req.ifr_name, options.ifname, IFNAMSIZ);
    if(ioctl(fd, SIOCGIFFLAGS, &req) == -1) {
        return -1;
    }
    req.ifr_flags |= IFF_PROMISC;
    if(ioctl(fd, SIOCSIFFLAGS, &req) == -1) {
        return -1;
    }
}

int engine_recv_loop(void)
{
    unsigned char buff[2048];
    struct ifreq req;
    struct sockaddr_ll  from;
    int  len, fromlen = sizeof(from);
    memset(&req, 0, sizeof(struct ifreq));
    strncpy(req.ifr_name, options.ifname, IFNAMSIZ);
    if(ioctl(fd, SIOCGIFINDEX, &req) == -1) {
        return -1;
    }
    while(1) {
        len = recvfrom(fd, buff, sizeof(buff), 0, (struct sockaddr *) &from, &fromlen);
        //printf("sll_ifindex=%d,ifr_ifindex=%d\n", from.sll_ifindex,req.ifr_ifindex);
        if(from.sll_ifindex == req.ifr_ifindex)
            engine_recv_handle(NULL, buff);
    }
}

int engine_recv_destory(void)
{
    close(fd);
}


int engine_mmap_init(void)
{
    struct tpacket_req req;
    int tpacket_version = TPACKET_V1;
    memset(&req, 0, sizeof(struct tpacket_req));
    if((fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1){
        return -1;
    }
    if(setsockopt(fd, SOL_PACKET, PACKET_VERSION, &tpacket_version, sizeof (int))){
        return -1;
    }
    if(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, options.ifname, sizeof(options.ifname))){
        return -1;
    }
    struct ifreq _req;
    memset(&_req, 0, sizeof(struct ifreq));
    strncpy(_req.ifr_name, options.ifname, IFNAMSIZ);
    if(ioctl(fd, SIOCGIFINDEX, &_req) == -1) {
        return -1;
    }
    struct sockaddr_ll  sll;
    memset(&sll, 0, sizeof(sll));
    sll.sll_family      = AF_PACKET;
    sll.sll_ifindex     = _req.ifr_ifindex;
    sll.sll_protocol    = htons(ETH_P_ALL);
    if (bind(fd, (struct sockaddr *) &sll, sizeof(sll)) == -1) { 
        return -1;
    }
#define PER_PACKET_SIZE 2048
#define BUFFER_SIZE (1024*1024*2)
    req.tp_block_size = 4096;
    req.tp_block_nr = BUFFER_SIZE/req.tp_block_size;
    req.tp_frame_size = PER_PACKET_SIZE;
    req.tp_frame_nr = BUFFER_SIZE/req.tp_frame_size;
    if(setsockopt(fd, SOL_PACKET, PACKET_RX_RING, (void *)&req, sizeof(req))){
        return -1;
    }
}

int engine_mmap_loop(void)
{
    unsigned char *buff;
    int idx = 0, i = 0;
    buff = (char *)mmap(0, BUFFER_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if(buff == MAP_FAILED) {
        return -1;
    }
    while(1) {
        struct tpacket_hdr* p_head = (struct tpacket_hdr*)(buff+ idx * PER_PACKET_SIZE);
        if(p_head->tp_status == TP_STATUS_USER)
            goto process_packet;
        struct pollfd pfd;
        pfd.fd = fd;
        pfd.events = POLLIN;
        pfd.revents = 0;
        if(poll(&pfd, 1, -1) < 0) {
            goto faile;
        }
process_packet:
        for(i=0; i < BUFFER_SIZE/PER_PACKET_SIZE; i++) {
            struct tpacket_hdr* p_head = (struct tpacket_hdr*)(buff+ idx * PER_PACKET_SIZE);
            if(!(p_head->tp_status & TP_STATUS_USER))
                break;
            engine_recv_handle(NULL, (unsigned char*)p_head+p_head->tp_mac);
            //printf("%d:%d,%d\n", idx, p_head->tp_status, p_head->tp_len);
            p_head->tp_len = 0;
            p_head->tp_status = TP_STATUS_KERNEL;
            idx++;
            idx %= BUFFER_SIZE/PER_PACKET_SIZE;
        }
    }

    munmap(buff, BUFFER_SIZE);
    return 0;
faile:
    munmap(buff, BUFFER_SIZE);
    return -1;

}

int engine_mmap_destory(void)
{
    close(fd);
}
